package WhiskeyGui;

import Charts.*;
import org.jfree.chart.ChartPanel;
import portBroadcasting.TempClient;

import javax.swing.*;
import java.awt.event.*;
import java.io.IOException;
import java.text.DecimalFormat;

/**
 * User: Mike
 * Date: 11/4/11
 * .
 * to create new chart... add a non palette object: ChartPanel.. select both of the check boxes
 * start jar with more memory by ---  java -jar <program> -xmsx 512
 */

// todo: grind down pour spout on parrot
// todo: use visocity as a measure of abv
// todo: get temp of distillate with arduino
// todo:

// todo: use the amount the still is charge to calculate the fraction of heads & middles in the run. then update the pie chart

// todo: had problems last time when it was writing. maybe i should create a fake mulitcaster
//           use existing multicaster, but read in data from file & broadcast it out
// todo: make sure it doesn't trigger off of notes
//           check to make sure this is done
// todo     ave abv

// todo: plot abv per bottle    bar chart    - (abv & ml should be done calculating... need to update bar chart)

// todo: delete the rate by bottle code... is redundant

// todo: try to get a area chart, with a line chart. use area chart for showing bottle number

public class MainGui {

    // GUI Variables
    private static final double MILLI_TO_MIN = (1.0 / 1000.0) * (1.0 / 60.0);
    private static final String DATA_CACHE_PATH = "C:\\WhiskeyData\\output.txt";

    public JPanel ideaGui2;
    public JPanel distTempPanel;
    private JPanel recDataPanel;
    private JPanel distDataPanel;
    private JPanel wortPanel;
    private JButton stopRecordingButton, startRecordingButton, updateButton;
    private JComboBox bottle, boilerTemp, distTemp, measAbv;
    private JTabbedPane Pane1;
    private JEditorPane editorPane1;
    private JButton updateGraphButton;
    private ChartPanel chartPanelBar;
    private ChartPanel chartPanelArea, chartPanelPie, chartPanelTime;
    private JTextField columnC, columnF, ambientC, ambientF;
    private JTextField chartRangeTxt, flowRateByRate, wortAbv, vaporAbv, corrAbv;
    private JComboBox flowRateVol;
    private JComboBox oG, tG, oT, tT;
    private JComboBox chartRange;
    private JComboBox flowRateTime;
    private JRadioButton autoUpdateChartRadioButton;
    private JRadioButton freezeChartRadioButton;
    private JRadioButton forceRecordData;
    private JTextField notes;
    private JRadioButton goodAbvMeasurement;
    private JTextField heatLevel;
    private JTextField needleValve;
    private JComboBox bottleVol;
    private JTextField flowRateByBottle;
    private JRadioButton newJar;
    private JTextField flowRateByJar;
    private JComboBox jarVol;
    private JTextField vaporAbvCorr;
    private JComboBox washAbv;
    private JComboBox washVolume;
    ChartArea cArea;
    ChartBar cBar;
    ChartPie cPie;
    //    CorrVaporAbvTemperature cTime;
    ChartScatter cTime;
    private boolean autoUpdateChart = false;
    BottleCalcs bottleCalcs;
    TempCorrections tempCorrections;

    static boolean writeOut = false;
    boolean readingTemp = false;
    private long bottleUpdateTime = 0;
    private long bottleUpdateTimePrevious = 0;
    private int bottlePrevious = 0;
    private double kalmanC = 0.0;
    private long lastMeasTime = 0;

    private Writer writer = new Writer();

//    private String timeSpan;
//    private Thread writeThread;

    DataCache dataCache;

    static double lastWrittenTemp = 1.1;
    static String lastWrittenBoilTemp = "1.1";
    static String lastWrittenBottleNum = "0";
    static String lastWrittenAbv = "0.0";
    static String lastWrittenFlowRate = "0.0";

    static final Double minTempChangeForWrite = 0.25;

    private Long sensorReadTime;
    private Long jarTime;
    private long milliTime = 0;


    private void createUIComponents() {
        setLook();
        this.cArea = new ChartArea("Area Chart");
        this.cBar = new ChartBar("Bar Chart");
        this.cPie = new ChartPie("Pie Chart");
//        this.cTime = new CorrVaporAbvTemperature("Time Chart");
        this.cTime = new ChartScatter("Time Chart");
        this.chartPanelBar = new ChartPanel(this.cBar.getChart());
        this.chartPanelArea = new ChartPanel(this.cArea.getChart());
        this.chartPanelPie = new ChartPanel(this.cPie.getChart());
        this.chartPanelTime = new ChartPanel(this.cTime.getChart());
    }

    private static void setLook() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            System.out.println("Error setting native LAF: " + e);
        }
    }

    public MainGui(JPanel recDataPanel) {
        this.recDataPanel = recDataPanel;
        bottleCalcs = new BottleCalcs();
        this.dataCache = new DataCache(DATA_CACHE_PATH);
        this.tempCorrections = new TempCorrections();
        columnC.setBorder(null);
        columnF.setBorder(null);
        ambientC.setBorder(null);
        ambientF.setBorder(null);
        corrAbv.setBorder(null);
        flowRateByRate.setBorder(null);
        flowRateByBottle.setBorder(null);
        flowRateByJar.setBorder(null);
        chartRangeTxt.setBorder(null);
        vaporAbv.setBorder(null);
        vaporAbvCorr.setBorder(null);
        // todo: set the notes text to wrap... not sure how!
        boilerTemp.setSelectedIndex(22);
        distTemp.setSelectedIndex(30);
        flowRateVol.setSelectedIndex(30);
        chartRange.setSelectedIndex(1);
        dataCache.setTimeSpan("10 min");

        startRecordingButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                writeOut = true;
            }
        });
        updateButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                double[][] data = new double[][]{{1.0, 3.0, 3.0, 5.0, 5.0, 5.0, 3.0, 3.0}, {7.0, 7.0, 7.0, 8.0, 8.0, 8.0, 10.0, 10.0}, {4.0, 3.0, 2.0, 3.0, 6.0, 3.0, 4.0, 3.0}};
                cArea.updateChart(data);
                chartPanelArea.setChart(cArea.getChart());
                chartPanelPie.setChart(cPie.getChart2());
                chartPanelPie.validate();
                dataCache.Update();
                cTime.updateChartL1R2(dataCache.getmilliTime(), dataCache.getColumnC(), dataCache.getVaporAbvCorr(), dataCache.getCorrAbvTime(), dataCache.getCorrAbv());
                chartPanelTime.setChart(cTime.getChart());
                //---------------------------------------- Bar Chart abv, volume, ...
                String[] barChartTitles = {"Bottle abv", "Bottle Flow Rate", "Bottle Volume"};
                String[] barChartCategories = new String[dataCache.getBottleAbv().size()];
                for (int i = 1; i <= dataCache.getBottleAbv().size(); i++)
                    barChartCategories[i - 1] = "Bottle " + i;
//                cBar.updateChart(dataCache.getBottleAbv(), dataCache.getBottleAbv(), dataCache.getBottleAbv(), titles, categories);
                cBar.updateChart(
                        dataCache.getBottleCalcs().getBottleAbv(),
                        dataCache.getBottleCalcs().getBottleRateByRate(),
                        dataCache.getBottleCalcs().getBottleVol(),
                        barChartTitles,
                        barChartCategories
                );
//                cBar.updateChart(bottleCalcs.getBottleAbv(), bottleCalcs.getBottleRateByRate(), bottleCalcs.getBottleVol(), titles, categories);
                chartPanelBar.setChart(cBar.getChart());
            }
        });
        ambientC.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                super.focusGained(e);
                if (!readingTemp)
                    temperature();
                readingTemp = true;
            }
        });
        stopRecordingButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                writeOut = false;
            }
        });
        oG.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String tmpS = oG.getSelectedItem().toString();
                Double og = tempCorrections.GetSG(Double.valueOf(tmpS), oT.getSelectedItem().toString());
                tmpS = tG.getSelectedItem().toString();
                Double tg = tempCorrections.GetSG(Double.valueOf(tmpS), tT.getSelectedItem().toString());
                DecimalFormat df1 = new DecimalFormat("#.####");
                wortAbv.setText(String.valueOf(df1.format((og - tg) * 105 * 1.25)));
            }
        });
        chartRange.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                dataCache.setTimeSpan(chartRange.getSelectedItem().toString());
                double[][] data = new double[][]{{1.0, 3.0, 3.0, 5.0, 5.0, 5.0, 3.0, 3.0}, {7.0, 7.0, 7.0, 8.0, 8.0, 8.0, 10.0, 10.0}, {4.0, 3.0, 2.0, 3.0, 6.0, 3.0, 4.0, 3.0}};
                cArea.updateChart(data);
                chartPanelArea.setChart(cArea.getChart());
                chartPanelPie.setChart(cPie.getChart2());
                chartPanelPie.validate();
                //---------------------------------------- ColumnT vs sensorReadTime
                dataCache.Update();
//                cTime.updateChart(dataCache.getmilliTime(), dataCache.getColumnC(), dataCache.getVaporAbv());
//                cTime.updateChart(dataCache.getmilliTime(), dataCache.getColumnC(), dataCache.getVaporAbv(), dataCache.getKalmanCArr());
//                cTime.updateChart(dataCache.getmilliTime(), dataCache.getColumnC(), dataCache.getVaporAbv(), dataCache.getVaporAbvCorr());
                cTime.updateChartL1R2(dataCache.getmilliTime(), dataCache.getColumnC(), dataCache.getVaporAbvCorr(), dataCache.getCorrAbvTime(), dataCache.getCorrAbv());

                // todo: move the vapor abv off of the left axis and over to the right
                // todo: eventually will probably want to get rid of the temperature plot all together. put bottle there instead, or volume?
                // todo: maybe put in a kalman filter for the bottle number, and map to int, to fix problems with wrong value (or put in a popup to check)
                chartPanelTime.setChart(cTime.getChart());
                //----------------------------------------
            }
        });

        bottle.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (Integer.valueOf(bottle.getSelectedItem().toString()) == 1) {
                    // todo: seems like we should not initialise here.. need to rethink this
//                    bottleCalcs = new BottleCalcs(System.currentTimeMillis(), Double.valueOf(flowRateByRate.getText()), Integer.valueOf(bottle.getSelectedItem().toString()));
                } else
                    bottleCalcs.newBottle(Integer.valueOf(bottle.getSelectedItem().toString()), Double.valueOf(flowRateByRate.getText()), Double.valueOf(corrAbv.getText()), Double.valueOf(bottleVol.getSelectedItem().toString()));
                flowRateByBottle.setText(String.valueOf(bottleCalcs.getBottleRateByBottle().get(bottleCalcs.getBottleRateByBottle().size() - 1)));
            }
        });

        newJar.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                DecimalFormat df = new DecimalFormat("###.#");
                if (jarTime != null)
                    flowRateByJar.setText(String.valueOf(df.format(Double.valueOf(jarVol.getSelectedItem().toString()) / (1.0 * System.currentTimeMillis() - 1.0 * jarTime) / MILLI_TO_MIN)));
                jarTime = System.currentTimeMillis();
                newJar.setSelected(false);
            }
        });

        measAbv.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String measAbvS = measAbv.getSelectedItem().toString();
                if (Double.valueOf(getBoilerTemp()) >= -1 &
                        Double.valueOf(measAbvS) >= -1 &
                        Double.valueOf(getDistTemp()) >= -1 &
                        Double.valueOf(oG.getSelectedItem().toString()) >= -1 &
                        Double.valueOf(tG.getSelectedItem().toString()) >= -1 &
                        Double.valueOf(oT.getSelectedItem().toString()) >= -1 &
                        Double.valueOf(tT.getSelectedItem().toString()) >= -1 &
                        Double.valueOf(getBottle()) >= -1
                        ) {
                    corrAbv.setText(String.valueOf(tempCorrections.GetABV(Double.valueOf(measAbvS), getDistTemp())));
                }
            }
        });
        distTemp.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String measAbvS = measAbv.getSelectedItem().toString();
                if (Double.valueOf(getBoilerTemp()) >= -1 &
                        Double.valueOf(measAbvS) >= -1 &
                        Double.valueOf(getDistTemp()) >= -1 &
                        Double.valueOf(oG.getSelectedItem().toString()) >= -1 &
                        Double.valueOf(tG.getSelectedItem().toString()) >= -1 &
                        Double.valueOf(oT.getSelectedItem().toString()) >= -1 &
                        Double.valueOf(tT.getSelectedItem().toString()) >= -1 &
                        Double.valueOf(getBottle()) >= -1
                        ) {
                    corrAbv.setText(String.valueOf(tempCorrections.GetABV(Double.valueOf(measAbvS), getDistTemp())));
                }
            }
        });
        flowRateVol.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                DecimalFormat df = new DecimalFormat("###.#");
                Double volume = Double.valueOf(flowRateVol.getSelectedItem().toString());
                Double time = Double.valueOf(flowRateTime.getSelectedItem().toString()) / 60.0;
                double output = volume / time * 1.0;
                output = Double.valueOf(df.format(output));
                flowRateByRate.setText(String.valueOf(output));
            }
        });
        flowRateTime.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                DecimalFormat df = new DecimalFormat("###.#");
                Double volume = Double.valueOf(flowRateVol.getSelectedItem().toString());
                Double time = Double.valueOf(flowRateTime.getSelectedItem().toString()) / 60.0;
                double output = volume / time * 1.0;
                output = Double.valueOf(df.format(output));
                flowRateByRate.setText(String.valueOf(output));
            }
        });
        autoUpdateChartRadioButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                freezeChartRadioButton.setSelected(false);
                autoUpdateChartRadioButton.setSelected(true);
                autoUpdateChart = true;
            }
        });
        freezeChartRadioButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                freezeChartRadioButton.setSelected(true);
                autoUpdateChartRadioButton.setSelected(false);
                autoUpdateChart = false;
            }
        });
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("MainGui");
        MainGui gui = new MainGui(null);
        frame.setContentPane(gui.ideaGui2);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);


    }

    private void temperature() {
        ensureEventThread();
        Runnable readRun = new Runnable() {
            public void run() {
                long startTime = System.currentTimeMillis();
                System.out.println("about to start loop to read in temps");
                Temperatures temps = new TempClient();
                lastMeasTime = System.currentTimeMillis();
                GetTemps(temps, startTime);
                Double[] obs;
                obs = new Double[2];
                obs[0] = getColumnC();
                obs[1] = 0.0;
//                KalmanFilter kf = new KalmanFilter(obs, obsCovariance());                                            // kalman
                while (true) {
                    try {
                        Thread.sleep((long) 1000);            // sleep for X ms
                    } catch (InterruptedException e) {
                        System.out.println("can't sleep for some reason");
                    }
//                    WhiskeyGui.Data.timeS = String.valueOf(((currentTime - startTime) / 1000.0));
                    GetTemps(temps, startTime);
//                    kf.predict(transMatrix(), transCov());                                                           // kalman
//                    kf.update(observation(), obsMatrix(), obsCovariance(), projection());                                     // kalman
//                    System.out.println("kalman extimation of column temp: " + kf.getStateVector()[0]);                                 // kalman
//                    Data.kalmanC = (double)kf.getStateVector()[0];                                                                              // kalman
                    lastMeasTime = System.currentTimeMillis();
                }
            }
        };
        Thread tempThread = new Thread(readRun);
        tempThread.start();
    }

    public void GetTemps(Temperatures tempsensor, Long startTime) {
        final double calibrationC = 0.0;                          // was (-0.1)
        final double calibrationF = calibrationC * 0.55;          //  5/9 = 0.5555555

        tempsensor.updateTemps();
        DecimalFormat df = new DecimalFormat("###.##");

        double temp = Double.valueOf(tempsensor.getColumnTempC()) + calibrationC;
        temp = Double.valueOf(df.format(temp));
        setColumnC(temp);

        temp = Double.valueOf(tempsensor.getColumnTempF()) + calibrationF;
        temp = Double.valueOf(df.format(temp));
        setColumnF(temp);

        temp = Double.valueOf(tempsensor.getAmbientTempC());
        temp = Double.valueOf(df.format(temp));
        setAmbientC(temp);

        temp = Double.valueOf(tempsensor.getAmbientTempF());
        temp = Double.valueOf(df.format(temp));
        setAmbientF(temp);

//        Double rate = Double.valueOf(flowRateVol.getSelectedItem().toString()) * (1.0 / Double.valueOf(flowRateTime.getSelectedItem().toString())) * (1.0 / 60.0);
//        flowRateByRate.setText(String.valueOf(rate));

//        double runningTime = (1.0 / 1000.0) * (1.0 / 60.0) * (System.currentTimeMillis() - startTime);
        sensorReadTime = tempsensor.getTime();
//        Data.timeS = String.valueOf(runningTime);
//        Data.notes = notes.getText();
        double tempChange = getColumnC() - lastWrittenTemp;
        if (!bottle.getSelectedItem().equals(bottlePrevious))
            bottleUpdateTime = sensorReadTime;
        if (dataHasChanged(tempChange)) {
            lastWrittenTemp = getColumnC();
            lastWrittenBoilTemp = getBoilerTemp();
            lastWrittenBottleNum = getBottle();
            lastWrittenAbv = getCorrAbv();
            lastWrittenFlowRate = getFlowRateByRate();
            try {
                if (getColumnC() != 0.0) {
                    writer.WriteData(writeOut, this);
                    notes.setText("");
                    goodAbvMeasurement.setSelected(false);
                    forceRecordData.setSelected(false);
                }
                if (autoUpdateChart) {
                    double[][] data = new double[][]{{1.0, 3.0, 3.0, 5.0, 5.0, 5.0, 3.0, 3.0}, {7.0, 7.0, 7.0, 8.0, 8.0, 8.0, 10.0, 10.0}, {4.0, 3.0, 2.0, 3.0, 6.0, 3.0, 4.0, 3.0}};
                    cArea.updateChart(data);
                    chartPanelArea.setChart(cArea.getChart());
                    chartPanelPie.setChart(cPie.getChart2());
                    chartPanelPie.validate();
                    //---------------------------------------- ColumnT vs sensorReadTime
                    dataCache.Update();
                    cTime.updateChartL1R2(dataCache.getmilliTime(), dataCache.getColumnC(), dataCache.getVaporAbvCorr(), dataCache.getCorrAbvTime(), dataCache.getCorrAbv());
                    chartPanelTime.setChart(cTime.getChart());
                    //----------------------------------------
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        setTempTextSafe();
    }

    private boolean dataHasChanged(double tempChange) {
        // main idea here is to check if any text box values have changed except for the notes.
        // if notes is changed, then don't record until the force is selected.

        boolean dataChanged = Math.abs(tempChange) >= minTempChangeForWrite ||                      // has the temp changed enough?
                !lastWrittenBoilTemp.contentEquals(getBoilerTemp()) ||                              // has the boiler temp changed?
                !lastWrittenBottleNum.contentEquals(getBottle()) ||                                 // has the bottle number changed?
                !lastWrittenAbv.contentEquals(getCorrAbv()) ||                                      // has the CorrAbv changed?
                !lastWrittenFlowRate.contentEquals(getFlowRateByRate())                             // has the FlowRateChanged?
                ;
        boolean newNoteCreated = !notes.getText().equals("");
        boolean forceRecord = forceRecordData.isSelected();
        return ((dataChanged && !newNoteCreated) || forceRecord);
    }

    // this is where vapor abv & vapor abv corrected gets set
    private void setTempTextSafe() {
        Runnable r = new Runnable() {
            public void run() {
                VaporAbvConvCorr vAbv = new VaporAbvConvCorr(String.valueOf(correctColumnTemp(getColumnC())));
                try {
                    DecimalFormat df = new DecimalFormat("###.##");
                    long dt = (sensorReadTime - milliTime);
//                    Double tVelocity = (getColumnC() - getColumnC()) / dt;                            // commented out because it's unused for some reason (probably kalman?)
                    setVaporAbv(String.valueOf(df.format(Double.valueOf(vAbv.getVaporAbv()))));
                    if (vAbv.getVaporAbvCorr() != null)
                        setVaporAbvCorr(String.valueOf(df.format(Double.valueOf(vAbv.getVaporAbvCorr()))));
                    else
                        setVaporAbvCorr("0");
                    milliTime = sensorReadTime;
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        };
        SwingUtilities.invokeLater(r);
    }

    private double correctColumnTemp(double columnC) {
        return columnC - 0.25;
    }

    private void ensureEventThread() {
        if (SwingUtilities.isEventDispatchThread()) {
            return;
        }
        throw new RuntimeException("file");
    }

    public Long getSensorReadTime() {
        return sensorReadTime;
    }

    public double getColumnC() {
        return Double.valueOf(columnC.getText());
    }

    public double getColumnF() {
        return Double.valueOf(columnF.getText());
    }

    public double getAmbientC() {
        return Double.valueOf(ambientC.getText());
    }

    public double getAmbientF() {
        return Double.valueOf(ambientF.getText());
    }

    public double getKalmanC() {
        return kalmanC;
    }

    public void setColumnC(double temp) {
        if (!(Double.isNaN(temp)))
            columnC.setText(String.valueOf(temp));
    }

    public void setColumnF(double temp) {
        if (!(Double.isNaN(temp)))
            columnF.setText(String.valueOf(temp));
    }

    public void setAmbientC(double temp) {
        if (!(Double.isNaN(temp)))
            ambientC.setText(String.valueOf(temp));
    }

    public void setAmbientF(double temp) {
        if (!(Double.isNaN(temp)))
            ambientF.setText(String.valueOf(temp));
    }


    public void setVaporAbv(String input) {
        if (!(input == null))
            vaporAbv.setText(input);
    }

    public void setVaporAbvCorr(String input) {
        if (!(input == null))
            vaporAbvCorr.setText(input);
    }


//    public String getAmbientC() {
//        return String.valueOf(ambientDC);
//    }
//
//    public String getAmbientF() {
//        return String.valueOf(ambientDF);
//    }

    public String getBottle() {
        return bottle.getSelectedItem().toString();
    }

    public String getCorrAbv() {
        return corrAbv.getText();
    }

    public String getVaporAbv() {
        return vaporAbv.getText();
    }

    public String getVaporAbvCorr() {
        return vaporAbvCorr.getText();
    }

    public String getDistTemp() {
        return distTemp.getSelectedItem().toString();
    }

    public String getBoilerTemp() {
        return boilerTemp.getSelectedItem().toString();
    }

    public String getFlowRateByRate() {
        return flowRateByRate.getText();
    }

    public String getFlowRateByBottle() {
        if (flowRateByBottle.getText().equals("null"))
            return "0";
        return flowRateByBottle.getText();
    }

    public String getFlowRateByJar() {
        return flowRateByJar.getText();
    }

    public void setFlowRateByJar(JTextField flowRateByJar) {
        this.flowRateByJar = flowRateByJar;
    }

    public String getHeatLevel() {
        return heatLevel.getText();
    }

    public String getNeedleValve() {
        return needleValve.getText();
    }

    public String getNotes() {
        return notes.getText();
    }

    public String getBottleVol() {
        return bottleVol.getSelectedItem().toString();
    }

    public boolean getGoodAbvMeasurement() {
        return goodAbvMeasurement.isSelected();
    }

}
